#define MODULEOC
#define _KERNEL_
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/namei.h>
#include <linux/mnt_namespace.h>
#include <linux/mount.h>
#include <linux/init_task.h>
#include <linux/slab.h>
#include <linux/path.h>
#include <linux/fs_struct.h>
#include <linux/errno.h>     /* error codes */
#include <linux/cdev.h>      /* char device stuff */
#include <asm/uaccess.h>  /* copy_to_user() */
#include <linux/version.h>

#include "device_file.h"
#include "hide.h"

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
static struct path g_nd_path;
#else
static struct nameidata g_nd;
#endif

struct dentry* g_dentry;
struct dentry* g_parent_dentry;
static int readdir_parent (struct file *file, void *buf, filldir_t filler);
filldir_t g_filler;

struct file_operations* g_orig_fops;
struct inode_operations* g_orig_iops;
struct file_operations* g_orig_fops_parent;

struct  hidden_element hidden;

//==========================================================================================================

static int set_dentry_pointer (char * path)
{
    struct task_struct *pp = &init_task;
    struct vfsmount *pmnt =pp->fs->root.mnt;
    struct dentry *pd =pp->fs->root.dentry;
    if (pmnt ==NULL)
    {
        printk(KERN_ALERT "FSHider-driver: MOUNT == NULL\n");
        return 1;
    }

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
    int error = vfs_path_lookup(pd, pmnt, path,  0, &g_nd_path);
#else
    int error = vfs_path_lookup(pd, pmnt, path,  0, &g_nd);
#endif
    if(error)
    {
        printk( KERN_ALERT "FSHider-driver: Can't access file: %s\n", path);
        return 3;
    }

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
    g_dentry = g_nd_path.dentry;
#else
    g_dentry = g_nd.path.dentry;
#endif

    return 0;
}
    
//==========================================================================================================

ssize_t new_read (struct file *file, char __user * u, size_t t, loff_t *ll)
{
	printk( KERN_ALERT "FSHider-driver: Entered in new_read\n");
	return -2;
}

ssize_t new_write (struct file * file, const char __user * u, size_t t, loff_t *ll)
{
	printk( KERN_ALERT "FSHider-driver: Entered in new_write\n");
	return -2;
}

int new_release (struct inode * inode, struct file *file)
{
	printk( KERN_ALERT "FSHider-driver: Entered in new_release \n");
	return -2;
}

int new_readdir (struct file *file, void *buf, filldir_t filler)
{
	printk( KERN_ALERT "FSHider-driver: Entered in new_readdir \n");
	return -2;
}

int new_open (struct inode * inode, struct file * file)
{
	printk( KERN_ALERT "FSHider-driver: Entered in new_open \n");
	return -2;
}

int new_mmap (struct file *file, struct vm_area_struct *area)
{
	printk( KERN_ALERT "FSHider-driver: Entered in new_mmap \n");
	return -2;
}

int new_rmdir (struct inode *new_inode,struct dentry *new_dentry)
{
	printk( KERN_ALERT "FSHider-driver: Entered in new_rmdir \n");
	return -2;
}

int new_getattr (struct vfsmount *mnt, struct dentry * new_dentry, struct kstat * ks)
{
	printk( KERN_ALERT "FSHider-driver: Entered in new_getattr \n");
	return -2;
}

static struct file_operations new_fops =
{
    .owner = THIS_MODULE,
	.readdir=new_readdir,
	.release=new_release,
	.open=new_open,
	.read=new_read, 
	.write=new_write,
	.mmap=new_mmap,
};

static struct inode_operations new_iops =
{
	.getattr=new_getattr,
	.rmdir=new_rmdir,
};
  
static struct file_operations new_fops_parent =
{
    .owner = THIS_MODULE,
    .readdir = readdir_parent,
};
    
//===========================================================================================================

static int my_filler (void *buf, const char *name, int namelen, loff_t offset,u64 ux64, unsigned ino)
{
    struct dentry* pd;
    struct qstr Current_Name;

    Current_Name.name=name;
    Current_Name.len=namelen;
    Current_Name.hash=full_name_hash (name, namelen);

    pd = d_lookup(g_parent_dentry, &Current_Name);
    
    if (pd != NULL)
    {
        if (hidden.i_ino == pd->d_inode->i_ino) 
        {
            return 0;
        }
    }

    return g_filler (buf, name, namelen, offset, ux64, ino);
}

//==========================================================================================================

static int readdir_parent (struct file *file, void *buf, filldir_t filler)
{
    if (!file->f_op || !file->f_op->readdir)
        goto out;

    g_parent_dentry = file->f_dentry;

    g_filler = filler;

    return file->f_dentry->d_sb->s_root->d_inode->i_fop->readdir(file, buf, my_filler);

out: 
    return -ENOTDIR;

}

//==========================================================================================================

int hook_func (char* path)
{
    if (hidden.i_ino != 0) 
    {
        unhide_all();
    }

    if (set_dentry_pointer (path))
    {
        return 1;
    }

    hidden.path = (char *)kmalloc(strlen(path), GFP_USER);
    strncpy(hidden.path, path, strlen(path));
    hidden.i_ino = g_dentry->d_inode->i_ino;


    g_orig_fops_parent = (struct file_operations*)g_dentry->d_parent->d_inode->i_fop;
    g_dentry->d_parent->d_inode->i_fop = &new_fops_parent;

    g_orig_fops = (struct file_operations*)g_dentry->d_inode->i_fop;
    g_dentry->d_inode->i_fop=&new_fops;

    g_orig_iops = (struct inode_operations*)g_dentry->d_inode->i_op;
    g_dentry->d_inode->i_op=&new_iops;

    printk( KERN_ALERT "FSHider-driver:   hided INODE = %li, PATH = %s", hidden.i_ino, hidden.path);

    return 0;
}

//============================================================================================================

void unhide_all ()
{
    printk( KERN_ALERT "FSHider-driver: unhided INODE = %li, PATH = %s", hidden.i_ino, hidden.path);

    if (g_orig_fops_parent) g_dentry->d_parent->d_inode->i_fop = g_orig_fops_parent;
    if (g_orig_fops) g_dentry->d_inode->i_fop = g_orig_fops;
    if (g_orig_iops) g_dentry->d_inode->i_op = g_orig_iops;

    hidden.i_ino = 0;
    kfree (hidden.path);
    return;
}

